
import time
import unittest

ONE_WEEK_IN_SECONDS = 7 * 86400
VOTE_SCORE = 432

'''
1.3.1 发布文章
发布文章函数post_article()
1）创建一个新的文章id（自增）；
2）将文章id和用户存储到set中；
3）设置文章过期时间为一周；
4）设置文章信息到hash中；
5）文章根据发布时间和评分排序
'''
def post_article(conn, user, title, link):
    article_id = str(conn.incr('article:'))
    voted = 'voted:' + article_id
    conn.sadd(voted, user)
    conn.expire(voted, ONE_WEEK_IN_SECONDS)
    # 设置文章信息到hash中
    now = time.time()
    article = 'article:' + article_id
    conn.hset(article, mapping={
        'title': title,
        'link': link,
        'poster': user,
        'time': now,
        'votes': 1,
    })
    # 文章根据发布时间和评分排序
    conn.zadd('score:', {article: now + VOTE_SCORE})
    conn.zadd('time:', {article: now})
    return article_id

'''
1.3.1 对文章投票
投票函数article_vote()
1）计算投票截止时间cutoff；
2）获取文章的投票时间大于截止时间，不能再进行投票了，返回；
3）对zset的文章评分zincrby();
4）对hash的文章数加1hincrby();
'''
def article_vote(conn, user, article):
    cutoff = time.time() - ONE_WEEK_IN_SECONDS
    if conn.zscore('time:', article) < cutoff:
        return

    article_id = article.partition(':')[-1]
    if conn.sadd('voted:' + article_id, user):
        conn.zincrby('score:', VOTE_SCORE, article)
        conn.hincrby(article, 'votes', 1)

'''
1.3.2 获取文章
获取文章的函数get_articles()
1）参数page为第几页，order的默认参数为score
2）计算文章的开始索引、结束索引
3）取出文章（倒序，分值从大到小排序）
4）遍历文章，取出分数最高的文章
'''
ARTICLES_PER_PAGE = 25
def get_articles(conn, page, order='score:'):
    start = (page-1) * ARTICLES_PER_PAGE
    end = start + ARTICLES_PER_PAGE - 1

    ids = conn.zrevrange(order, start, end)
    articles = []
    for id in ids:
        article_data = conn.hgetall(id)
        article_data['id'] = id
        articles.append(article_data)
    return articles

'''
1.3.3 对文章进行分组
从群组里面移除文章add_remove_groups()：
1）如果文章在列表to_add[]中，添加进去，列表默认文空；
2）如果文章在列表to_remove[]中，从列表移除，列表默认文空；
'''
def add_remove_groups(conn, article_id, to_add=[], to_remove=[]):
    article = 'article:' + article_id
    for group in to_add:
        conn.sadd('group:' + group, article)
    for group in to_remove:
        conn.srem('group:' + group, article)
'''
对文章进行分组：
1）为每个群组创建一个key;
2）检查是否已经缓存了排序结果；
3）根据评分或发布时间对文章排序；
4）设置过期时间为60秒；
5）调用上面定义的获取文章函数
'''
def get_group_articles(conn, group, page, order='score:'):
    key = order + group
    if not conn.exists(key):
        conn.zinterstore(key, ['group:' + group, order],  aggregate='max',)
        conn.expire(key, 60)
    return get_articles(conn, page, key)


#--------------- 单元测试 ----------------
class TestCh01(unittest.TestCase):
    # 创建连接
    def setUp(self):
        import redis
        self.conn = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)
        print("------建立redis连接------")
        print()

    # 释放链接
    def tearDown(self):
        del self.conn
        print("------释放redis连接------")

    def test_article_functionality(self):
        conn = self.conn
        import pprint
        # 发布文章
        article_id = str(post_article(conn, 'sunyifeng', '文章标题', 'http://www.google.com'))
        print("我们发布了一个新文章，文章的id:", article_id)
        print()
        self.assertTrue(article_id)

        print("从hash中获取发布的文章:")
        r = conn.hgetall('article:' + article_id)
        print(r)
        print()
        self.assertTrue(r)

        # 测试对文章投票
        article_vote(conn, 'other_user', 'article:' + article_id)
        print("我们对文章进行了投票, 投票结果是:", end=' ')
        v = int(conn.hget('article:' + article_id, 'votes'))
        print(v)
        print()
        self.assertTrue(v > 1)

        print("目前得票数最高的文章是:")
        # 获取得票最高的文章
        articles = get_articles(conn, 1)
        # 美化打印输出
        pprint.pprint(articles)
        print()

        self.assertTrue(len(articles) >= 1)
        # 将该文章从投票结果中移除
        add_remove_groups(conn, article_id, ['new-group'])
        print("我们把文章添加到了一个新的组, 新组的文章包括:")
        # 将文章进行分组
        articles = get_group_articles(conn, 'new-group', 1)
        pprint.pprint(articles)
        print()
        self.assertTrue(len(articles) >= 1)

        #释放conn中的对象
        to_del = (
            conn.keys('time:*') + conn.keys('voted:*') + conn.keys('score:*') + 
            conn.keys('article:*') + conn.keys('group:*')
        )
        if to_del:
            conn.delete(*to_del)

if __name__ == '__main__':
    unittest.main()
